Chapitre 4. Les concepts et méthodes qui font le sel de la reproductibilité dans l’ingénierie logicielle
Mettre en place une démarche d’analyses reproductibles présuppose quelques réflexes et une hygiène du développement qui repose sur des principes et des concepts communs à toute exploitation d’un capital code.
4.1 Versionner
Versionner est l’opération qui consiste à sécuriser des écrits (souvent du code, appelé parfois “sources”) en en conservant la trajectoire et l’histoire grâce à un système de gestion de versions. Morceaux de textes après morceaux de textes, conserver la mémoire de la construction d’un fichier a le double avantage d’être instructif sur sa genèse mais laisse également la possibilité de revenir à des états antérieurs. Versionner pourrait consister à sauvegarder des fichiers dans un répertoire donné avec un motif de nom de fichier par exemple, mais les systèmes de gestion de version proposent plus de fonctionnalités et de possibilités autorisant ainsi une dimension collaborative et exploratoire.

Figure 4.1: De l’intérêt de versionner - source : Jorge Cham, http://phdcomics.com
Premièrement, dans l’immense majorité des cas, le système de gestion de version est installé et hébergé sur un serveur en plus d’une installation pour chaque utilisateur. Les contributeurs communiquent avec ce serveur pour synchroniser leur production de code avec celle du serveur distant, qui les centralise toutes. Il existe donc à chaque instant deux endroits de stockage distincts (côté utilisateur et côté serveur) qui, pour peu qu’ils communiquent régulièrement et se synchronisent dans leurs contenus, permettent de s’affranchir du risque matériel (perte, vol, casse…).
Ensuite, ce système permet aussi à plusieurs utilisateurs de travailler simultanément sur la même base de code.

Figure 4.2: Git : l’expérimentation par les branches - source : Jennifer Gilbert (jay_gee)
Enfin, le système de gestion de version permet également aux utilisateurs d’experimenter des solutions alternatives dans un système dit de “branches”. À l’image de la rédaction du scénario de fin alternative d’un film, une copie est dérivée du répertoire des sources par un développeur dans une branche pour y tester une idée ou une hypothèse. Si le scénario proposé est adopté, la dérivation est “reversée” au tronc commun. Si en cours d’écriture le “film” avance, il est toujours possible de rapatrier à chaque instant les états d’avancement du “tronc” dans “sa” branche. Cela permet de relever des incohérences avec le fil narratif. Et finalement, si la fin alternative d’un développeur n’est pas adoptée, cela n’impactera pas le fil narratif principal : les expérimentations resteront dans une branche, sans venir interférer avec le scénario principal. Cela permet aux développeurs d’expérimenter le coeur léger : on peut “tout casser” dans une branche sans impacter le fonctionnement général du logiciel.
C’est parce qu’ils intègrent l’historique des fichiers et leur sauvegarde sécurisée, que les systèmes de gestion de versions sont des outils indispensables et centraux de la valorisation du capital code des organisations qui les utilisent.
4.2 Documenter
Documenter consiste à expliciter des lignes de code dans un langage intelligible par l’homme. Mieux organiser les intentions d’un programme comme un flux de pensées tel qu’il serait écrit pour des travaux littéraires est une démarche dite de “programmation lettrée”. Pour ce faire, il existe des outils qui permettent de tisser du texte avec du code executé. La documentation qui en résulte traduit alors la logique sous-jacente. Par exemple, ce document n’a pas été rédigé dans un traitement de texte mais dans un format RMarkdown dans lequel il est possible de faire exécuter des instructions R.
Par exemple, l’instruction sample(x = 1:10, size = 1)
permet d’effectuer un tirage aléatoire. Elle utilise la fonction sample()
qui prend deux paramètres séparés par une virgule : x
, les chiffres parmi lesquels le tirage sera effectué (ici, les chiffres de 1 à 10) et size
, le nombre de chiffres à tirer (ici, un). En mêlant du texte et du code, on peut faire la démonstration de la commande et de son execution. Lorsque ce document sera mis en forme, la commande sample(x = 1:10, size = 1)
sera executée et le résultat du tirage sera 6.
Par ailleurs, il ne s’agit pas seulement d’écrire en français ou en anglais ce qu’un programme est supposé faire, mais également de faire l’effort de fournir des exemples “génériques” qui fonctionneraient pour tout utilisateur qui souhaiterait s’approprier le programme en l’adaptant à ses besoins.
4.3 Factoriser
Factoriser se comprend au sens mathématique du terme, c’est à dire à trouver des communs et les regrouper pour clarifier le propos. En programmation, cela peut se traduit par l’écriture d’une fonction. À l’image d’une recette de cuisine, une fonction est une suite séquencée et ordonnée d’étapes qui prennent des “ingrédients” en entrée et permettent de réaliser un produit fini en sortie. L’écriture d’une fonction est un investissement en temps : quand une action est réalisée régulièrement - un type de graphique, une transformation de donnée, voire un rapport ou une étude pour ce qui concerne les activités du datalab - il est opportun d’étudier le temps nécessaire à son écriture, en comparaison au temps qu’elle est susceptible de faire économiser à terme.

Figure 4.3: Faut-il faire une fonction ? - source : Xkcd, https://xkcd.com/1205/
4.4 Tester
Tester consiste, assez intuitivement, à s’assurer que les résultats produits (souvent par une fonction) sont bien ceux escomptés mais surtout à prévoir les comportements à adopter par le programme ou le logiciel si les dits tests échouent.
Certaines approches de programmation consistent à écrire les tests avant même la première ligne de code (approches dites “test-driven”). La démarche de programmation consistera alors à écrire les fonctions qui n’échouent pas aux tests.
4.5 Cacher
Cacher est l’opération qui consiste à stocker des informations dans une mémoire temporaire dans le but d’économiser le recalcul de ces mêmes informations si elles n’ont vraisemblablement pas changé. C’est cette opération qui permet par exemple de parcourir une page web en étant déconnecté d’internet si elle était préalablement chargée.
Pour l’analyste ou l’administrateur, cette opération s’avère très utile pour les flux de traitement de données volumineuses : un pipeline de données est construit étapes par étapes et reconstruire tout le pipeline n’est pas chronophage dans la mesure où, par un effet cliquet, il est possible de se reposer sur le dernier état connu s’il n’a pas été modifié.
Cacher est également indispensable dans une perspective d’économie de la ressource computationnelle.
4.6 Packager
Packager, ou mettre en paquet, consiste à mettre à disposition des utilisateurs une brique fonctionnelle immédiatement utilisable aux fins pour lesquelles elle a été imaginée. Le package est portable, c’est à dire qu’il doit pouvoir être utilisé sur plusieurs systèmes d’exploitations. Si le concept de mise en paquet est identique pour nombre de langages, sa mise en pratique opérationnelle diffère énormément d’un langage à l’autre.
4.7 Intégrer
L’intégration est l’opération qui consiste à raccorder le plus d’éléments (base de données, programmes, applications, serveurs…) entre eux pour aboutir à la production de la donnée ou de l’information et automatiser leur exécution. L’intégration continue permet, au moindre changement n’importe où dans la chaîne d’assemblage de reconstituer rapidement et de façon automatisée le produit fini. Elle se substitue à toute opération manuelle comme un clic ou un dépôt de fichiers qui seraient un obstacle et sujets à des erreurs.
4.8 Conteneuriser (éventuellement)
La conteneurisation est l’opération qui met en boîte une chaîne d’opérations intégrées pour la désolidariser complètement de toute attache. C’est une mécanique qui permet de figer dans le temps l’exploitation qui est faite d’un outil (ou d’une suite d’outils). Le conteneur est livré avec toutes les ressources nécessaires pour faire fonctionner ce qu’il contient, fixant dans le temps le système d’exploitation et les versions de logiciels et/ou de paquets. Conteneurisé, un produit logiciel se veut éternel : on peut à tout moment le relancer, en l’état, sans se soucier des changements locaux qui seraient effectués sur la machine faisant tourner le code. Autrement dit, une analyse effectuée avec la version 1.0.0 d’un paquet ou d’un logiciel, si elle est conteneurisée, ne sera pas impactée par la mise à jour vers la version 2.0.0 sur la machine éxécutant ce code conteneurisé.
4.9 Le principe de séparation des préoccupations
L’ensemble des techniques explicitées ci-avant sont insuffisantes si les programmes et applications développées ne respectent pas les principes de la séparation des préoccupations. Cette pratique largement reconnue et mise en oeuvre dans l’ingénierie logicielle éprouve un concept d’informatique théorique qui préconise de réaliser des développements qui soient modulaires, chacun adressant un aspect particulier du problème. Par exemple, “tracer une pyramide des âges aux couleurs de la charte graphique et l’exporter en format jpeg” ferait l’objet de trois préoccupations différentes : tracer un graphique qui soit une pyramide des âges, appliquer une charte graphique à un graphique et exporter un graphique. Modulariser permet de faciliter la maintenance, le deboggage et rend la lecture du code plus aisée.